import re
import socket
import json
from collections import OrderedDict
from datetime import datetime


class page_not_found(BaseException):
    pass


class unsupported_method(BaseException):
    pass


class MultipleValueDict(dict):
    def __setitem__(self, key, value):
        if key not in self:
            values = list()
            super().__setitem__(key, values)

        values = super().__getitem__(key)
        values.append(value)

    def __getitem__(self, item):
        values = super().__getitem__(item)
        if len(values) == 1:
            return values[0]

        return values


class HttpRequest:
    def __init__(self):
        self.method = None
        self.GET = MultipleValueDict()
        self.POST = MultipleValueDict()
        self.cookies = dict()
        self.path = None
        self.full_path = None
        self.arguments = None
        self.body = None
        self.protocol = None
        self.version = 0.0
        self.headers = dict()

    def __setitem__(self, key, value):
        self.headers[key] = value

    def __getitem__(self, item):
        return self.headers[item]

    def __iter__(self):
        return self.headers.__iter__()

    def __str__(self):
        html = "{} {} {}/{}\r\n".format(self.method, self.full_path, self.protocol, self.version)
        for header in self.headers:
            html += "{}: {}\r\n".format(header, self.headers[header])
        html += "\r\n"

        return html


# The descriptions for http status codes.
HTTP_DESC = {
    200: "Success",
    301: "Moved Permanently",
    302: "Found",
    400: "Bad Request",
    404: "Not Found",
    405: "Method Not Allowed",
    500: "Internal Server Error"
}


class HttpResponse:
    def __init__(self, status=200, **options):
        self.status = status
        self.body = None
        # self.headers = dict(options)
        self.headers = MultipleValueDict(options)
        self.cookies = dict()
        self["Server"] = "light-django"
        self['Date'] = datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")
        self['Content-Type'] = "text/html; charset=UTF-8"
        self['Cache-Control'] = "no-cache, must-revalidate, max-age=0"

    def __setitem__(self, key, value):
        self.headers[key] = value

    def __getitem__(self, item):
        return self.headers[item]

    def __iter__(self):
        return self.headers.__iter__()

    def __str__(self):
        html = "HTTP/1.1 {} {}\r\n".format(self.status, HTTP_DESC[self.status])
        if self.body:
            self['Content-Length'] = len(self.body)

        for header in self.headers:
            value = self.headers[header]
            if isinstance(value, list):
                for v in value:
                    html += "{}: {}\r\n".format(header, str(v))
            else:
                html += "{}: {}\r\n".format(header, value)
        html += "\r\n"
        if self.body:
            html += self.body

        return html


class JsonResponse(HttpResponse):
    def __init__(self, data):
        super().__init__(status=200)
        self['Content-Type'] = "application/json;charset=utf-8"
        self.body = json.dumps(data)


class HttpResponseRedirect(HttpResponse):
    def __init__(self, redirect_url):
        super().__init__(status=302)
        self["Location"] = redirect_url


# The shortcuts of the http responses.
HttpResponseNotFound = HttpResponse(status=404)
HttpResponseBadRequest = HttpResponse(status=400)
HttpResponseServerError = HttpResponse(status=500)
HttpResponseMethodNotAllowed = HttpResponse(status=405)


class UnsupportedProtocol(BaseException):
    pass


def serialize(http_response):
    """
    Serialize the HttpResponse to string.
    :param http_response:
    :return:
    """
    assert isinstance(http_response, HttpResponse)
    return str(http_response)


def parse_header(http_str):
    """
    Deserialize the HTTP string to a HttpRequest object.
    :param http_str:
    :return:
    """

    request = HttpRequest()

    strs = http_str.split("\r\n\r\n", 1)
    if len(strs) == 2:
        header, body = strs
    else:
        header, body = strs[0], None

    request.body = body
    headers = header.split("\r\n")
    regexp = "(?P<method>GET|POST|PUT|HEADER)\\s(?P<path>[^\\s\?]+)(\?(?P<arguments>[^\\s]*))?\\s(?P<protocol>HTTP)\/(?P<version>[1-9\.]+)"
    m = re.match(regexp, headers[0])
    if m is None:
        raise UnsupportedProtocol

    request.method = m.group("method")
    request.path = m.group("path")
    request.full_path = request.path
    request.arguments = m.group("arguments")
    if m.group("arguments"):
        request.full_path += "?" + m.group("arguments")

    request.protocol = m.group("protocol")
    request.version = float(m.group("version"))
    for line in headers[1:]:
        k, v = line.split(": ", 1)
        request[k] = v

    return request


def deserialize2(sock):
    def parse_arguments(line, args):
        if not line:
            return None

        for kv in line.split("&"):
            try:
                key, value = kv.split("=", 1)
            except:
                key, value = kv, None

            args[key] = value

    assert isinstance(sock, socket.socket)
    data = b''

    while True:
        r_data = sock.recv(1)
        if not r_data or len(r_data) == 0:
            raise ConnectionError

        data += r_data
        if data[-4:] == b'\r\n\r\n':
            break

    print("RECV: \n" + data.decode("utf-8"))
    request = parse_header(data.decode("utf-8"))
    if "Content-Length" in request:
        body_length = int(request['Content-Length'])
        body = sock.recv(body_length)
        if not body or len(body) != body_length:
            return None
        request.body = body.decode("utf-8")
        print(request.body + "\n")

    if request.method == "GET":
        parse_arguments(request.arguments, request.GET)
    elif request.method == "POST":
        request.arguments = request.body
        parse_arguments(request.arguments, request.POST)

    return request


if __name__ == "__main__":
    http_header = "GET / HTTP/1.1\r\n" \
                  "Host: www.jobbole.com\r\n" \
                  "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36\r\n" \
                  "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n" \
                  "Accept-Encoding: gzip, deflate\r\n" \
                  "Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7\r\n" \
                  "Dnt: 1\r\n" \
                  "Proxy-Connection: keep-alive\r\n" \
                  "Upgrade-Insecure-Requests: 1\r\n" \
                  "X-Lantern-Version: 4.9.0\r\n\r\n"

    request = parse_header(http_header)
    print(str(request))
